/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "ScriptSemantics_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/inout/format/fmt.h"

#include <cassert>

namespace simodo::interpret
{
    void ScriptSemantics_abstract::initiateCallbacks()
    {
        _cycle_condition_callback = 
                [this](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    name_index_t        condition_variable_index = stack().top();
                    variable::Variable  condition_variable       = stack().variable(condition_variable_index).origin();
                    bool                condition = false; 
                    
                    try
                    {
                        if (!condition_variable.value().isNull() && !condition_variable.value().isBool())
                            condition_variable = inter().expr().convertVariable(condition_variable, variable::ValueType::Bool);
                    }
                    catch(...)
                    {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        throw;
                    }
                    
                    if (condition_variable.value().isNull())
                        condition = false;
                    else if (condition_variable.value().isBool())
                        condition = condition_variable.value().getBool();
                    else
                        assert(false);

                    stack().pop();

                    if (condition)
                        inter().addFlowLayer(*_cycle_data_stack.back().pop_body, _cycle_body_callback);
                    else {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                    }
                };

        _cycle_body_callback =
                [this](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign || !checkInterpretType(InterpretType::Preview)) {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    inter().addFlowLayer(*_cycle_data_stack.back().pop_expression, _cycle_condition_callback);
                };

        _for_source_callback =
                [this](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    name_index_t source_index = stack().top();

                    _cycle_data_stack.back().source = stack().variable(source_index).origin();

                    if (!_cycle_data_stack.back().source.value().isArray() && !_cycle_data_stack.back().source.value().isObject())
                        _cycle_data_stack.back().source = inter().expr().convertVariable(_cycle_data_stack.back().source, variable::ValueType::Array);

                    if (_cycle_data_stack.back().source.type() == variable::ValueType::Object) {
                        std::shared_ptr<variable::Object> object   = _cycle_data_stack.back().source.value().getObject();
                        const variable::Variable          iterator = object->getVariableByName(u"__iterator__");

                        if (!iterator.name().empty() && iterator.type() == variable::ValueType::Function) {
                            std::shared_ptr<variable::Object> function_object  = iterator.value().getObject();
                            if (function_object->variables().size() > 1
                             && function_object->variables()[0].type() == variable::ValueType::IntFunction)
                                function_object->variables()[0].value().getIntFunctionParent() = object;

                            _cycle_data_stack.back().iterator = iterator;
                            stack().push(_cycle_data_stack.back().iterator);

                            boundary_index_t   boundary_index  = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_FUNCTION_FRAME);

                            stack().push(_cycle_data_stack.back().source);
                            inter().expr().callPreparedFunction(
                                    boundary_index, 
                                    notification(),
                                    checkInterpretType(InterpretType::Preview) 
                                    || _cycle_data_stack.back().iterator.origin().name().substr(0,2) == u"__",
                                    _for_iterator_callback);
                            return;
                        }
                    }

                    if (prepareForSource())
                        inter().addFlowLayer(*_cycle_data_stack.back().pop_body, _for_body_callback);
                };

        _for_iterator_callback =
                [this](const FlowLayerInfo & flow)
                {
                    // Завершаем обработку функции итератора после оператора return
                    stack().pop(); // функция итератора

                    if (flow.error_sign) {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    // Обработка возврата итератора
                    if (inter().expr().return_value().isArray()) {
                        std::shared_ptr<variable::Array> iterator_pair = inter().expr().return_value().getArray();
                        if (iterator_pair->values().size() >= 2) {
                            const variable::Value & condition_value = iterator_pair->values()[1];
                            if (condition_value.isBool()) {
                                if (condition_value.getBool() == true) {
                                    variable::Variable & variable = stack().variable(_cycle_data_stack.back().variable_index);

                                    variable.value() = iterator_pair->values()[0];
                                    if (!_cycle_data_stack.back().typed_variable.value().isNull()
                                     && _cycle_data_stack.back().typed_variable.type() != variable.value().type())
                                        variable.value() = inter().expr().convertVariable(variable, _cycle_data_stack.back().typed_variable.type()).value();

                                    inter().addFlowLayer(*_cycle_data_stack.back().pop_body, _for_body_callback);
                                    return;
                                }
                                stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                                _cycle_data_stack.pop_back();
                                return;
                            }
                        }
                    }

                    assert(_cycle_data_stack.back().pop_source && !_cycle_data_stack.back().pop_source->branches().empty());
                    inout::TokenLocation loc = _cycle_data_stack.back().pop_source->branches().front().token().location();

                    stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                    _cycle_data_stack.pop_back();

                    throw AnalyzeException("ScriptSemantics_abstract::_for_iterator_callback", 
                                            loc.makeLocation(inter().files()),
                                            inout::fmt("The iterator of the iteration should return values in the form of "
                                            "a vector in the format: [value, bool : condition]"));
                };

        _for_body_callback =
                [this](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    assert(_cycle_data_stack.back().source.value().isArray() || _cycle_data_stack.back().source.value().isObject());

                    if (!_cycle_data_stack.back().iterator.value().isNull()) {
                        if (!checkInterpretType(InterpretType::Preview)) {
                            stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                            _cycle_data_stack.pop_back();
                            return;
                        }

                        stack().push(_cycle_data_stack.back().iterator);

                        boundary_index_t   boundary_index  = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_FUNCTION_FRAME);

                        stack().push(_cycle_data_stack.back().source);
                        inter().expr().callPreparedFunction(
                                boundary_index, 
                                notification(),
                                checkInterpretType(InterpretType::Preview) 
                                || _cycle_data_stack.back().iterator.origin().name().substr(0,2) == u"__",
                                _for_iterator_callback);
                        return;
                    }

                    _cycle_data_stack.back().source_counter += 1;

                    if (prepareForSource()) {
                        if (!checkInterpretType(InterpretType::Preview)) {
                            stack().clearBoundary(_cycle_data_stack.back().boundary_index);
                            _cycle_data_stack.pop_back();
                            return;
                        }

                        inter().addFlowLayer(*_cycle_data_stack.back().pop_body, _for_body_callback);
                    }
                };
    }

}
